/*
 * See the NOTICE file distributed with this work for additional
 * information regarding copyright ownership.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
*/

#include "lib_insim.h"

struct insim_t *insim_create()
{
	struct insim_t *I = malloc(sizeof(struct insim_t));
	if (I != NULL)
	{
	    memset(I, 0, sizeof(struct insim_t));
		return I;
	}

	return NULL;
}

int insim_destroy(struct insim_t *I)
{
    if (I->lfs.address != NULL)
        free(I->lfs.address);

    if (I->lfs.admin_password != NULL)
        free(I->lfs.admin_password);

	if (I != NULL)
		free(I);
	return 0;
}

int insim_connect(struct insim_t *I)
{
	if (insim_init(I) < 0)
        return -1;

	struct IS_ISI p;
	memset(&p, 0, sizeof(struct IS_ISI));
	p.size = sizeof(struct IS_ISI);
	p.type = 1;
	p.udpport = 0;
	p.flags = I->lfs.flags;
	p.interval = I->lfs.node_secs;
	p.prefix = I->lfs.prefix;
	memcpy(p.iname, PRODUCT_FULL, strlen(PRODUCT_FULL));
	memcpy(p.admin, I->lfs.admin_password, 16);

	if (insim_send(I, (const char *)&p, sizeof(struct IS_ISI)) < 0)
        return -1;

	struct IS_TINY v;
	memset(&v, 0, sizeof(struct IS_TINY));
	v.size = sizeof(struct IS_TINY);
	v.type = ISP_TINY;
	v.subT = TINY_VER;
	v.reqI = 1;
	if (insim_send(I, (char *)&v, sizeof(struct IS_TINY)) < 0)
        return -1;

	return 0;
}

int insim_recv(struct insim_t *I)
{
    /* packetise the TCP-stream */

    int rc;
    struct timeval select_timeout = I->socket.select_timeout;

    fd_set readfd, exceptfd;

    FD_ZERO(&readfd);
    FD_ZERO(&exceptfd);

	FD_SET(I->socket.s, &readfd);
	FD_SET(I->socket.s, &exceptfd);

	rc = select(I->socket.s + 1, &readfd, NULL, &exceptfd, &select_timeout);

    insim_hook_fire_prerecv(I);

    /* select output handling */
	if (rc < 0)
	{
	    fprintf(stderr, "insim_recv: Select error!\n");
	    return -1;
	}
	else if (rc > 0)
	{
	    if (FD_ISSET(I->socket.s, &readfd))
	    {
            // make local temp buffer
            char tbuf[PACKET_BUFFER_SIZE];
            memset(&tbuf, 0, PACKET_BUFFER_SIZE);
            // bytes read
            unsigned int bread = 0;

            // copy any half packet into the local buffer
            if (I->pbuf.bytes > 0)
            {
                bread = I->pbuf.bytes;
                memcpy(tbuf, I->pbuf.buffer, I->pbuf.bytes);
                //I->pbuf.bytes = 0;
                // clear left overs
                memset(I->pbuf.buffer, 0, PACKET_BUFFER_SIZE);

// repeat 1 line added
                if (I->internals.verbose)
                    printf("BUFFER: read in %u bytes\n", (unsigned char)bread);
            }

            // recv waiting bytes
            int retval = recv(I->socket.s + 1, tbuf + bread, PACKET_BUFFER_SIZE - (unsigned int)bread, 0);
            // deal with recv
            if (retval == 0)
            {
                if (I->pbuf.bytes == PACKET_BUFFER_SIZE)
                    fprintf(stderr, "BUFFER filled totally and was unparsed in previous iteration. Something got screwed.\n");
                // Connection has been closed
                return -1;
            }
            else if (retval > 0)
            {
                // The packetisation process;

                // read size of packet
                // check to see if buffer doesnt exceed the limits, and has a packet size. loop until not satisfied
                    // read packet into lua
                    // increase number of bytes read
                // check remaining sizes and
                    // copy remaining into global buffer and set number of bytes

                char *p = tbuf;
                bread = 0;
                // packet size
                unsigned char psize;

                unsigned int tbytes = I->pbuf.bytes + retval;
                I->pbuf.bytes = 0;

                if (I->internals.verbose)
                    printf("retval %d, total local buffer size: %u\n", retval, tbytes);

                while ((PACKET_BUFFER_SIZE >= (tbytes - bread - (unsigned char)*p)) && ((tbytes - bread - (unsigned char)*p) >= 0) && ((unsigned char)*p > 0))
                {
                    psize = (unsigned char)*p;

                    if (psize == 0)
                    {
                        printf("BUG: Packet size 0: %d\n", tbytes - bread - (unsigned char)*p);
                        return -1;
                    }

                    insim_hook_fire_recv(I, p, psize);

                    if (I->internals.verbose)
                        printf("Received %u, size %u\n", (unsigned char)*(p+1), (unsigned char)*p);

                    p += psize;
                    bread += psize;
                }

                int tout = (unsigned char)(tbytes - bread);
                if (tout < 0)
                {
                    printf("BUG: Read more bytes than available - this shouldn't happen, but we'd rather do this than crash. %u\n", tout);
                    return -1;
                }

                if (tout > PACKET_BUFFER_SIZE)
                {
                    printf("BUG: More bytes left over than available in global buffer. %u\n", tout);
                    return -1;
                }

                I->pbuf.bytes = (unsigned char)(tout);
                memcpy(I->pbuf.buffer, tbuf+bread, I->pbuf.bytes);

                if (I->internals.verbose)
                    printf("Buffer size: %d\n", I->pbuf.bytes);
            }
	    }
	    else if (FD_ISSET(I->socket.s, &exceptfd))
	    {
	        printf("insim_recv: Exception FD thrown\n");
	        return -1;
	    }
	}
    //my include (repeat)
    insim_hook_fire_thread(I);
	return 0;
}

int insim_disconnect(struct insim_t *I)
{
	struct IS_TINY p;
	memset(&p, 0, sizeof(struct IS_TINY));
	p.size = sizeof(struct IS_TINY);
	p.type = ISP_TINY;
	p.reqI = 0;
	p.subT = 2;

	insim_send(I, (char *)&p, sizeof(struct IS_TINY));
	insim_close(I);

	return 0;
}

int insim_send(struct insim_t *I, const char *buf, unsigned int size)
{
	if (I->internals.verbose)
        printf("Sending %u (Sizes: Sent %u, Actual %d)\n", (unsigned char)buf[1], (unsigned char)buf[0], size);

	return send(I->socket.s, buf, size, 0);
}

int insim_init(struct insim_t *I)
{
	struct sockaddr_in saddr;
#ifdef WIN32
   	WSADATA wsadata;

	if (WSAStartup(0x202, &wsadata) == SOCKET_ERROR)
	{
		WSACleanup();
		return WSAGetLastError();
	}
#endif

	/* create our listen socket */
	I->socket.s = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
	if (I->socket.s == INVALID_SOCKET)
	{
		return -1;
	}

    struct hostent *hp;
    hp = gethostbyname(I->lfs.address);

    memset(&saddr, 0, sizeof(saddr));
	saddr.sin_family = AF_INET;

	if (hp != NULL)
        saddr.sin_addr.s_addr = *((unsigned long*)hp->h_addr);
    else
        saddr.sin_addr.s_addr = inet_addr(I->lfs.address);

	saddr.sin_port = htons(I->lfs.port);

    int t = connect(I->socket.s, (struct sockaddr *) &saddr, sizeof(saddr));

    if (t < 0)
    {
        fprintf(stderr, "Connection to '%s' failed\n", I->lfs.address);
        return -1;
    }

    return 0;
}

int insim_close(struct insim_t *I)
{
	if (I->socket.s != INVALID_SOCKET)
		closesocket(I->socket.s);

	I->socket.s = INVALID_SOCKET;
#ifdef WIN32
	WSACleanup();
#endif
	return INVALID_SOCKET;
}

int insim_hook_fire_create(struct insim_t *I)
{
    struct list_t *c;
    struct hooks_t *h = NULL;
    c = I->hooks;

    if (I->internals.verbose)
        printf("Loading hooks\n");

    while(c != NULL)
    {
        h = c->data;

        if (h->fp_create != NULL)
        {
            if ((I->internals.verbose) && (h->name != NULL))
                printf("Loading '%s'\n", h->name);
            h->fp_create(I, &(h->ctx));
        }

        c = c->next;
    }

    return 0;
}

int insim_hook_fire_connected(struct insim_t *I)
{
    struct list_t *c;
    struct hooks_t *h;
    c = I->hooks;

    if (I->internals.verbose)
        printf("Calling hook connected funcs\n");

    while(c != NULL)
    {
        h = c->data;

        void *ctx = h->ctx;
        if (h->fp_connected != NULL)
        {
            h->fp_connected(I, ctx);
        }
        c = c->next;
    }

    return 0;
}

int insim_hook_fire_disconnected(struct insim_t *I)
{
    struct list_t *c;
    struct hooks_t *h;
    c = I->hooks;

    if (I->internals.verbose)
        printf("Calling hook disconnected funcs\n");

    while(c != NULL)
    {
        h = c->data;
        if (h->fp_disconnected != NULL)
            h->fp_disconnected(I, h->ctx);
        c = c->next;
    }

    return 0;
}

int insim_hook_fire_recv(struct insim_t *I, void *data, unsigned int size)
{
    struct list_t *c;
    struct hooks_t *h;
    c = I->hooks;

    //printf("Calling hook recv funcs\n");

    while(c != NULL)
    {
        h = c->data;

        if ((I->internals.verbose) && (h->name != NULL))
            printf("Recv '%s'\n", h->name);

        if (h->fp_recv != NULL)
            h->fp_recv(I, h->ctx, data, size);
        c = c->next;
    }

    return 0;
}

int insim_hook_fire_close(struct insim_t *I)
{
    struct list_t *c;
    struct hooks_t *h;
    c = I->hooks;

    if (I->internals.verbose)
        printf("Calling hook close funcs\n");

    while(c != NULL)
    {
        h = c->data;

        if ((I->internals.verbose) && (h->name != NULL))
            printf("Closing '%s'\n", h->name);

        if (h->fp_close != NULL)
            h->fp_close(I, h->ctx);
        c = c->next;
    }

    return 0;
}

int insim_hook_fire_prerecv(struct insim_t *I)
{
    struct list_t *c;
    struct hooks_t *h;
    c = I->hooks;

    if (I->internals.verbose)
        printf("Calling hook prerecv funcs\n");

    while(c != NULL)
    {
        h = c->data;

        if ((I->internals.verbose) && (h->name != NULL))
            printf("PreRecv '%s'\n", h->name);

        if (h->fp_prerecv != NULL)
            h->fp_prerecv(I, h->ctx);
        c = c->next;
    }

    return 0;
}

//my include (repeat)
int insim_hook_fire_thread(struct insim_t *I)
{
    struct list_t *c;
    struct hooks_t *h;
    c = I->hooks;

    if (I->internals.verbose)
        printf("Calling hook thread funcs\n");

    while(c != NULL)
    {
        h = c->data;

        void *ctx = h->ctx;
        if (h->fp_thread != NULL)
        {
            h->fp_thread(I, ctx);
        }
        c = c->next;
    }

    return 0;
}
